---
name: html-to-images
description: Export a single-file HTML slide deck as individual PNG images — one per slide. Renders in headless Chrome with fullPage screenshot, then slices with Pillow. Use when the user wants slide images for social media, docs, or any use case that doesn't need PPTX.
---
# HTML to Images Skill
Export any single-file HTML slide deck as individual PNG images. Each slide becomes its own file: `slide-01.png`, `slide-02.png`, etc.
## How It Works
1. **Headless Chrome** renders the full HTML page at once (`fullPage: true`) → one tall PNG
2. **Pillow** slices it into N equal strips and saves each as a numbered PNG
No PPTX, no python-pptx. Just clean image files ready for anywhere.
---
## Requirements
| Tool | Install |
|---|---|
| Node.js | https://nodejs.org |
| Python 3 | https://python.org |
| Puppeteer | `npm install puppeteer` (auto-handled) |
| Pillow | `pip install pillow` |
| Chrome or Edge | Already installed on most systems |
---
## Step-by-Step Instructions
### 1. Ask the user for inputs
You need:
- **HTML file path** — the source presentation
- **Number of slides** — how many slides the deck has
- **Output folder** — where to save the images (default: a `slides/` subfolder next to the HTML)
- **Output format** — PNG (default) or JPEG
- **Resolution** — default 1920×1080. Ask only if they want something different (e.g. 2560×1440 for retina, 1080×1080 for square social).
Auto-detect Chrome from these paths (check in order):
- `C:/Program Files/Google/Chrome/Application/chrome.exe`
- `C:/Program Files (x86)/Google/Chrome/Application/chrome.exe`
- `C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe`
- `/usr/bin/google-chrome`
- `/usr/bin/chromium-browser`
### 2. Install puppeteer if needed
```bash
node -e "require('puppeteer')" 2>&1
npm install puppeteer --save-dev
```
### 3. Write the screenshot script
Write `_screenshot.mjs` to the same folder as the HTML:
```js
import puppeteer from 'puppeteer';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const HTML_PATH = path.join(__dirname, '');
const W = ; // e.g. 1920
const H = ; // e.g. 1080
const browser = await puppeteer.launch({
executablePath: '',
headless: true,
args: ['--no-sandbox', `--window-size=${W},${H}`],
});
const page = await browser.newPage();
await page.setViewport({ width: W, height: H, deviceScaleFactor: 1 });
await page.goto(`file:///${HTML_PATH.replace(/\\/g, '/')}`, {
waitUntil: 'networkidle0',
timeout: 30000,
});
// Wait for fonts and transitions to settle
await new Promise(r => setTimeout(r, 2500));
// Freeze animations, hide fixed UI chrome
await page.addStyleTag({ content: `
* { animation: none !important; transition: none !important; }
body::after { display: none !important; }
#nav, #progress, nav, header[data-fixed] { display: none !important; }
` });
await page.screenshot({
path: path.join(__dirname, '_fullpage.png'),
fullPage: true,
});
console.log('Full page captured.');
await browser.close();
```
### 4. Write the slice script
Write `_slice_images.py` to the same folder:
```python
from PIL import Image
import os
FOLDER = r''
FULLPAGE = os.path.join(FOLDER, '_fullpage.png')
OUT_DIR = os.path.join(FOLDER, '') # e.g. 'slides'
SLIDES =
FORMAT = '' # 'PNG' or 'JPEG'
EXT = 'jpg' if FORMAT == 'JPEG' else 'png'
os.makedirs(OUT_DIR, exist_ok=True)
img = Image.open(FULLPAGE)
W, H = img.size
slide_h = H // SLIDES
print(f"Full image: {W}x{H}, {SLIDES} slides, {slide_h}px each")
print(f"Saving to: {OUT_DIR}")
for i in range(SLIDES):
top = i * slide_h
crop = img.crop((0, top, W, top + slide_h))
out_path = os.path.join(OUT_DIR, f'slide-{i+1:02d}.{EXT}')
if FORMAT == 'JPEG':
crop = crop.convert('RGB') # JPEG does not support transparency
crop.save(out_path, format=FORMAT, quality=95)
else:
crop.save(out_path, format=FORMAT)
print(f" Saved slide-{i+1:02d}.{EXT} (rows {top}-{top+slide_h})")
print(f"\nDone. {SLIDES} images saved to {OUT_DIR}/")
```
### 5. Run both scripts
```bash
node _screenshot.mjs
python _slice_images.py
```
### 6. Clean up temp files
After confirming the images look correct, delete:
- `_fullpage.png`
- `_screenshot.mjs`
- `_slice_images.py`
The output images in the `slides/` folder are kept.
---
## Output
```
slides/
├── slide-01.png
├── slide-02.png
├── slide-03.png
...
└── slide-10.png
```
---
## Common Issues
| Problem | Cause | Fix |
|---|---|---|
| All images identical | Using scroll + viewport clip | Always use `fullPage: true` + slice — never scroll |
| Slide count wrong | Slide height inconsistency in HTML | Ensure every slide is exactly `100vh` |
| JPEG has black background | Transparency not supported by JPEG | Skill auto-converts to RGB before saving |
| Chrome not found | Wrong exe path | Check Edge as fallback |